1 module hip.font.bmfont;
2 import hip.api.data.font;
3 import hip.api.renderer.texture;
4 import hip.error.handler;
5 
6 
7 class HipBitmapFont : HipFont
8 {
9     ///The atlas path is saved inside the class
10     string atlasPath;
11     ///This variable is defined when the atlas is being read
12     string atlasTexturePath;
13 
14     ///Use that property to know how many characters was read inside the atlas
15     uint charactersCount;
16 
17     private string error;
18 
19 
20     HipFontKerning kerning;
21 
22     bool loadAtlas(string data, string atlasPath)
23     {
24         import hip.util.string;
25         this.atlasPath = atlasPath;
26 
27         scope int advanceSpace(string data, int i)
28         {
29             while(i < data.length && (data[i].isWhitespace || data[i] == '='))
30                 i++;
31             return i;
32         }
33         scope int nextToken(string data, int i)
34         {
35             i = advanceSpace(data, i);
36             if(i >= data.length)
37                 return -1;
38             else if(data[i] == '"') //Find the '"'
39             {
40                 i++;
41                 while(i < data.length && data[i] != '"')
42                 {
43                     if(data[i] == '\\')
44                         i++;
45                     i++;
46                 }
47                 if(i < data.length)i++;
48                 
49             }
50             else if(data[i].isAlpha)
51             {
52                 while(i <= data.length && data[i].isAlpha)
53                     i++;
54             }
55             else if(data[i].isNumeric)
56             {
57                 while(i <= data.length && data[i].isNumeric)
58                     i++;
59             }
60             return i;
61         }
62 
63         scope int getNextInt(string data, ref int i)
64         {
65             import hip.util.conv;
66             int start = advanceSpace(data, i);
67             i = nextToken(data, start);
68             if(i != -1)
69             {
70                 return to!int(data[start..i]);
71             }
72             return i;
73         }
74         scope string getNextString(string data, ref int i)
75         {
76             int start = advanceSpace(data, i);
77             i = nextToken(data, start);
78             if(i != -1)
79             {
80                 if(data[start] == '"')
81                     return data[start+1..i-1];
82                 else
83                     return data[start..i];
84             }
85             return "";
86         }
87         string name;
88         int size;
89 
90         int bold, italic;
91         string charset;
92         int unicode;
93         int stretchH;
94         int smooth, aa;
95 
96         int paddingX, paddingY, paddingW, paddingH;
97         int spacingX, spacingY;
98         int outline;
99 
100         //Common
101         int lineHeight, base, scaleW, scaleH, pages, packed, alpha, red, green,blue;
102         //Page
103         int pageId;
104         //chars
105         int count;
106 
107         int index = 0;
108         enum Context
109         {
110             info,
111             common,
112             page,
113             chars,
114             kernings,
115             unknown
116         }
117         int context = Context.unknown;
118         string key;
119         while(index != -1 && index < data.length)
120         {
121             key = getNextString(data, index);
122             final switch(context)
123             {
124                 case Context.info:
125                 {
126                     switch(key)
127                     {
128                         case "face": 
129                             name = getNextString(data, index);
130                             break;
131                         case "size":
132                             size = getNextInt(data, index);
133                             break;
134                         case "bold":
135                             bold = getNextInt(data, index);
136                             break;
137                         case "italic":
138                             italic = getNextInt(data, index);
139                             break;
140                         case "charset":
141                             charset = getNextString(data, index);
142                             break;
143                         case "unicode":
144                             unicode = getNextInt(data, index);
145                             break;
146                         case "stretchH":
147                             stretchH = getNextInt(data, index);
148                             break;
149                         case "smooth":
150                             smooth = getNextInt(data, index);
151                             break;
152                         case "aa":
153                             aa = getNextInt(data, index);
154                             break;
155                         case "padding":
156                             paddingX = getNextInt(data, index);
157                             index++;
158                             paddingY = getNextInt(data, index);
159                             index++;
160                             paddingW = getNextInt(data, index);
161                             index++;
162                             paddingH = getNextInt(data, index);
163                             index++;
164                             break;
165                         case "spacing":
166                             spacingX = getNextInt(data, index);
167                             index++;
168                             spacingY = getNextInt(data, index);
169                             index++;
170                             break;
171                         case "outline":
172                             outline = getNextInt(data, index);
173                             break;
174                         default:
175                             goto checkUnknown;
176                     }
177                     break;
178                 }
179                 case Context.common:
180                 {
181                     switch(key)
182                     {
183                         case "lineHeight":
184                             lineHeight = getNextInt(data, index);
185                             break;
186                         case "base":
187                             base = getNextInt(data, index);
188                             break;
189                         case "scaleW":
190                             scaleW = getNextInt(data, index);
191                             break;
192                         case "scaleH":
193                             scaleH = getNextInt(data, index);
194                             break;
195                         case "pages":
196                             pages = getNextInt(data, index);
197                             break;
198                         case "packed":
199                             packed = getNextInt(data, index);
200                             break;
201                         case "alphaChnl":
202                             alpha = getNextInt(data, index);
203                             break;
204                         case "redChnl":
205                             red = getNextInt(data, index);
206                             break;
207                         case "greenChnl":
208                             green = getNextInt(data, index);
209                             break;
210                         case"blueChnl":
211                             blue = getNextInt(data, index);
212                             break;
213                         default:
214                             goto checkUnknown;
215                     }
216                     break;
217                 }
218                 case Context.page:
219                 {
220                     switch(key)
221                     {
222                         case "id":
223                             pageId = getNextInt(data, index);
224                             break;
225                         case "file":
226                             atlasTexturePath = getNextString(data, index);
227                             break;
228                         default:
229                             goto checkUnknown;
230                     }
231                     break;
232                 }
233                 case Context.chars:
234                 {
235                     //Advance "count"
236                     charactersCount = count = getNextInt(data, index);
237                     uint maxWidth = 0;
238                     for(int i = 0; i < count; i++)
239                     {
240                         HipFontChar ch;
241                         //Advance "char"
242                         index = nextToken(data, index);
243                         //id
244                         index = nextToken(data, index);
245                         ch.id = getNextInt(data, index);
246                         // x
247                         index = nextToken(data, index);
248                         ch.x = getNextInt(data, index);
249                         // y
250                         index = nextToken(data, index);
251                         ch.y = getNextInt(data, index);
252                         // width
253                         index = nextToken(data, index);
254                         ch.width = getNextInt(data, index);
255                         if(ch.width > maxWidth)
256                             maxWidth = ch.width;
257                         // height
258                         index = nextToken(data, index);
259                         ch.height = getNextInt(data, index);
260                         // xoffset
261                         index = nextToken(data, index);
262                         ch.xoffset = getNextInt(data, index);
263                         // yoffset
264                         index = nextToken(data, index);
265                         ch.yoffset = getNextInt(data, index);
266                         // xadvance
267                         index = nextToken(data, index);
268                         ch.xadvance = getNextInt(data, index);
269                         // page
270                         index = nextToken(data, index);
271                         ch.page = getNextInt(data, index);
272                         // chnl
273                         index = nextToken(data, index);
274                         ch.chnl = getNextInt(data, index);
275                         
276                         characters[ch.id] = ch;
277                     }
278                     auto space = ' ' in characters;
279                     if(space is null || (space.width == 0 && space.xadvance == 0))
280                         spaceWidth = maxWidth;
281                     else
282                         spaceWidth = space.xadvance > space.width ? space.xadvance : space.width;
283                     lineBreakHeight = lineHeight;
284                     context = Context.unknown;
285                     break;
286                 }
287                 case Context.kernings:
288                 {
289                     //Advance "count"
290                     index = nextToken(data, index);
291                     int kerningCount = getNextInt(data, index);
292                     for(int i = 0; i < kerningCount; i++)
293                     {
294                         //Advance "kerning "
295                         index = nextToken(data, index);
296 
297                         //first
298                         index = nextToken(data, index);
299                         int first = getNextInt(data, index);
300                         //second
301                         index = nextToken(data, index);
302                         int second = getNextInt(data, index);
303                         //amount
304                         index = nextToken(data, index);
305                         int amount = getNextInt(data, index);
306 
307                         if((first in kerning) is null)
308                             kerning[first] = HipCharKerning.init;
309                         kerning[first][second] = amount;
310                     }
311                     break;
312                 }
313                 //Tries to find the context
314                 checkUnknown: case Context.unknown:
315                     contextSwitch: switch(key)
316                     {
317                         static foreach(mem; __traits(allMembers, Context))
318                         {
319                             case mem:
320                                 context = __traits(getMember, Context, mem);
321                                 break contextSwitch;
322                         }
323                         default:
324                             assert(false, "Unknown key received: "~key);
325                     }
326                     continue;
327             }
328         }
329         
330 
331         return true;
332     }
333 
334     bool loadTexture(IHipTexture t)
335     {
336         texture = t;
337         int width = t.getWidth;
338         int height = t.getHeight;
339         if(width == 0 || height == 0)
340             return false;
341         
342         foreach(ref ch; characters)
343         {
344             if(ch.id != 0)
345             {
346                 ch.normalizedX = cast(float)ch.x/width;
347                 ch.normalizedY = cast(float)ch.y/height;
348                 ch.normalizedWidth = cast(float)ch.width/width;
349                 ch.normalizedHeight = cast(float)ch.height/height;
350             }
351         }
352         return true;
353     }
354 
355     string getTexturePath()
356     {
357         import hip.util.path;
358         string texturePath;
359         if(atlasTexturePath != "")
360         {
361             string atlasDir = atlasPath.dirName;
362             if(atlasDir != atlasPath)
363                 texturePath = atlasDir.joinPath(atlasTexturePath);
364             else
365                 texturePath = atlasTexturePath;
366         }
367         return texturePath;
368     }
369 
370     /**
371     *   This won't do anything in case of a bitmap font, as no one can change it.
372     */
373     override HipFont getFontWithSize(uint size)
374     {
375         HipBitmapFont ret = new HipBitmapFont();
376         ret.atlasPath = this.atlasPath;
377         ret.atlasTexturePath = this.atlasTexturePath;
378         ret.kerning = cast(HipFontKerning)this.kerning.dup;
379         ret.charactersCount = this.charactersCount;
380         ret._texture = cast(IHipTexture)this._texture;
381         return cast(HipFont)ret;
382     }
383     void readTexture(string texturePath = "")
384     {
385         texturePath = texturePath == "" ? getTexturePath() :  texturePath;
386         // auto t = new HipTexture();
387         // t.load(texturePath);
388         // loadTexture(t);
389     }
390 
391 
392     static HipBitmapFont fromFile(string atlasPath, string texturePath = "")
393     {
394         auto ret = new HipBitmapFont();
395         ret.loadAtlas("", atlasPath);
396         ret.readTexture(texturePath);
397         return ret;
398     }
399     
400     override int getKerning(const(HipFontChar)* current, const(HipFontChar)* next) const
401     {
402         return getKerning(dchar(current.id), dchar(next.id));
403     }
404     override int getKerning(dchar current, dchar next) const
405     {
406         const HipCharKerning* chKerning = current in kerning;
407         if(chKerning is null)
408             return 0;
409         const int* kerningValue = next in (*chKerning);
410         if(kerningValue is null)
411             return 0;
412         return *kerningValue;
413     }
414     
415 
416 }